// ***** BEGIN LICENSE BLOCK *****
// Version: MPL 1.1/GPL 2.0/LGPL 2.1
//
// The contents of this file are subject to the Mozilla Public License Version
// 1.1 (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// The Initial Developer of the Original Code is
//   Axel Nennker http://axel.nennker.de/ .
// Created by the Initial Developer are Copyright (C) 2011
// the Initial Developer. All Rights Reserved.

const CI = Components.interfaces;
const CC = Components.classes;
const CU = Components.utils;
const CR = Components.results;

const DEFAULT_USER_AGENT = "openid";

CU.import("resource://gre/modules/XPCOMUtils.jsm");
CU.import("resource://gre/modules/Services.jsm");

function log( str) {
//Services.console.logStringMessage("OpenID HttpListener: " +str);
}

function errlog( str) {
  Services.console.logStringMessage("OpenID HttpListener Error: " +str);
}

//function StreamInterceptor (aSubject) {
//    this.subject = aSubject;
//    this.interceptedData = [];
//}
//StreamInterceptor.prototype = {
//    originalListener: null,
//    subject: null,
// 
//    onDataAvailable: function (request, context, inputStream, offset, count) {
//        let binaryInputStream  = CC["@mozilla.org/binaryinputstream;1"].createInstance(CI.nsIBinaryInputStream),
//            storageStream      = CC["@mozilla.org/storagestream;1"].createInstance(CI.nsIStorageStream),
//            binaryOutputStream = CC["@mozilla.org/binaryoutputstream;1"].createInstance(CI.nsIBinaryOutputStream),
//            data;
// 
//        binaryInputStream.setInputStream(inputStream);
//        storageStream.init(8192, count, null);
//        binaryOutputStream.setOutputStream(storageStream.getOutputStream(0));
// 
//        data = binaryInputStream.readBytes(count);
//        this.interceptedData.push(data);
// 
//        binaryOutputStream.writeBytes(data, count);
// 
//        try { // XXX see moz-bug 492534
//            this.originalListener.onDataAvailable(request, context, storageStream.newInputStream(0), offset, count);
//        } catch (e) {
//            request.cancel(e.result);
//        }
//    },
// 
//    onStartRequest: function (request, context) {
//        try { // XXX see moz-bug 492534
//            this.originalListener.onStartRequest(request, context);
//        } catch (e) {
//            request.cancel(e.result);
//        }
//    },
// 
//    onStopRequest: function (request, context, statusCode) {
//    	log("StreamInterceptor " + this.interceptedData.join());
//        //ObserverService.notifyObservers(this.subject, "http-on-examine-response-content", this.interceptedData.join());
//        this.originalListener.onStopRequest(request, context, statusCode);
//    },
// 
//    QueryInterface: XPCOMUtils.generateQI([CI.nsIStreamListener, CI.nsISupports]),
//};

function OpenIDNascarHttpListener() {
  this.log = log;
  this.errlog = errlog;
  this.wrappedJSObject = this;
  this.log("constructor");
  Services.obs.addObserver(this, "profile-after-change", false);
}

OpenIDNascarHttpListener.prototype = {
  // properties required for XPCOM registration:
  classDescription: "OpenID HTTP Listener",
  classID:          Components.ID("{a1a39d60-4a6d-11e0-b8af-0800200c9a66}"),
  contractID:       "@openinfocard.com/httplistener-service;1",

  headerName: "User-Agent",

  userAgent: DEFAULT_USER_AGENT,
  instance: null,
  
  _xpcom_factory: {
    createInstance: function (outer, iid) {
      if (outer != null)
        throw Components.results.NS_ERROR_NO_AGGREGATION;
      if (!OpenIDNascarHttpListener.instance)
        OpenIDNascarHttpListener.instance = new OpenIDNascarHttpListener();
      return OpenIDNascarHttpListener.instance.QueryInterface(iid);
    },

    QueryInterface: XPCOMUtils.generateQI(
      [ Components.interfaces.nsISupports,
        Components.interfaces.nsIModule,
        Components.interfaces.nsIFactory ])
  },

  // [optional] an array of categories to register this component in.
  _xpcom_categories: [
    {
      category: "app-startup",
    }
  ],

  // QueryInterface implementation, e.g. using the generateQI helper
  QueryInterface: XPCOMUtils.generateQI(
    [ Components.interfaces.nsIObserver,
      Components.interfaces.nsISupports,
      Components.interfaces.nsISupportsWeakReference,
      Components.interfaces.nsIWebProgressListener]),

  wrappedJSObject: null,  // Initialized by constructor

  getWeakReference: function () {
    return Components.utils.getWeakReference(this);
  },

  getDomWindowFromChannel: function _getDomWindowFromChannel(aChannel) {
    try {
      var notificationCallbacks =
        aChannel.notificationCallbacks ? aChannel.notificationCallbacks : aChannel.loadGroup.notificationCallbacks;
   
      if (!notificationCallbacks)
        return null;
   
      var domWin;
      try {
        domWin = notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow);
      } catch(e) { return; } // safebrowsing and such
      return domWin;
    }
    catch (e) {
      this.errlog( "getDomWindowFromChannel: Exception= " + e);
      return null;
    }
  },
  
  getMainWindowFromChannel: function _getMainWindowFromChannel(channel) {
    try {
      var domWin = this.getDomWindowFromChannel(channel);
      if (!domWin) {
        this.log( "getMainWindowFromChannel: !domWin");
        return;
      }
      
      var topWindow = domWin.top;
      try {
        this.log( "getMainWindowFromChannel: topWindow.document.location.href=" + topWindow.document.location.href);
      } catch (e) {
        this.errlog( "getMainWindowFromChannel: Exception=" + e);
        return;
      }
  
      var mainWindow = topWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                             .getInterface(Components.interfaces.nsIWebNavigation)
                             .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
                             .rootTreeItem
                             .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                             .getInterface(Components.interfaces.nsIDOMWindow);
      try {
        this.log( "getMainWindowFromChannel: mainWindow.document.location.href=" + mainWindow.document.location.href);
      } catch (e) {
        this.errlog( "getMainWindowFromChannel: mainWindow Exception=" + e);
        return;
      }
      return mainWindow;
    } catch (e) {
        this.errlog( "getMainWindowFromChannel: Exception=" + e);
      }
    },

//  handleOpenIdConnectUserInfo : function _handleOpenIdConnectUserInfo(channel, mainWindow) {
//    this.log("handleOpenIdConnectUserInfo: start");
//	try {
//	  var url = channel.URI.spec;
//	  var referrer = channel.referrer.spec;
//	  this.log("handleOpenIdConnectUserInfo: url="+url+" referrer="+referrer);
//	} catch (e) {
//      this.log( "handleOpenIdConnectUserInfo: Exception=" + e);
//    }
//  },

  handleOpenID : function _handleOpenID(channel, mainWindow) {
    this.log("handleOpenID: start");
    try {
      var url = channel.URI.spec;
      var q = url.indexOf('?');
      if(q == -1) {
        this.log("handleOpenID: no query");
        return;
      }
      url = url.substring(q+1);
      this.log("handleOpenID: query=" + url);
      if(url.indexOf("openid.mode=id_res") == -1) {
        this.log("handleOpenID: no id_res");
        return;
      }
      var openid1_claimed_id;
      var claimed_id;
      var realm;
      var identity;
      var op_endpoint;
      var s = url.split('&');
      var v;
      for (var ii=0; ii<s.length; ii++) {
        var sub = s[ii];
        this.log("handleOpenID: "+sub);
        v = "openid1_claimed_id=";
        if (sub.indexOf(v) === 0) {
          openid1_claimed_id = sub.substring(v.length);
          this.log("handleOpenID: " + v + claimed_id);
        }
        v = "openid.claimed_id=";
        if (sub.indexOf(v) === 0) {
          claimed_id = sub.substring(v.length);
          this.log("handleOpenID: " + v + claimed_id);
        }
        v = "openid.op_endpoint=";
        if (sub.indexOf(v) === 0) {
          op_endpoint = sub.substring(v.length);
          this.log("handleOpenID: " + v + op_endpoint);
        }
        v = "openid.realm=";
        if (sub.indexOf(v) === 0) {
          realm = sub.substring(v.length);
          this.log("handleOpenID: " + v + realm);
        }
        v = "openid.identity=";
        if (sub.indexOf(v) === 0) {
          identity = sub.substring(v.length);
          this.log("handleOpenID: " + v + identity);
        }
      }
  
      if (!mainWindow.__openid__) {
        mainWindow.__openid__ = {};
      }
      mainWindow.__openid__.realm = realm;
      mainWindow.__openid__.claimed_id = (claimed_id) ? claimed_id : openid1_claimed_id;
      mainWindow.__openid__.identity = identity;
      mainWindow.__openid__.op_endpoint = op_endpoint;
    } catch (e) {
      this.errlog( "openid handleOpenId: exception=" + e);
    }
      
  },
  
  observe: function(subject, topic, data) {
    try {
        var channel = subject;
        this.log("Got observer topic: "+topic);

        if (topic == "http-on-modify-request") {
          if (!(channel instanceof CI.nsIHttpChannel)) return;
          try {
            this.log("Got http-on-modify-request: "+channel.URI.spec);
          } catch(e) {}
          var strUA = channel.getRequestHeader(this.headerName);
          if(strUA && strUA.length > 0) {
            if(strUA.indexOf(this.userAgent) != -1) {
              return;
            }

            strUA += " ";
          }
          strUA += this.userAgent;

          channel.setRequestHeader(this.headerName, strUA, false);

          return;
        } else if (topic == "http-on-examine-response") {
          try {
            var spec = "" + channel.URI.spec;
            this.log("Got http-on-examine-response: "+spec);
          } catch(e) {}
          if (!(channel instanceof CI.nsIHttpChannel)) return;

//          //https://oauthssodemo.appspot.com/oauthcallback#state=/&access_token=ya29.AHES6ZRh6Bil3xyqX59yjOwqCkv5kYbmFQ-3q4AlsB5eQFuw&token_type=Bearer&expires_in=3600
//          if ((spec.indexOf("https://www.googleapis.com/oauth2/v1/userinfo?") != -1) || 
//        		  (spec.indexOf("https://oauthssodemo.appspot.com/oauthcallback") != -1)) 
//          {
//          	this.log("httplistener contentType: " + channel.contentType);
//        	let interceptor = new StreamInterceptor(subject);
//            interceptor.originalListener = subject.QueryInterface(CI.nsITraceableChannel).setNewListener(interceptor);
//          }
          var mainWindow = this.getMainWindowFromChannel(channel);
          if (mainWindow) {
            this.handleOpenID(channel, mainWindow);
//            this.handleOpenIdConnectUserInfo(channel, mainWindow);
          } else {
//            this.log("!!!mainWindow");
          }
          return;
        } else if (topic == "app-startup") {
          this.log("Got app-startup");
        } else if (topic == "profile-after-change") {
          this.log( "Got profile-after-change");
          var observerService = Components.classes["@mozilla.org/observer-service;1"]
            .getService(CI.nsIObserverService);
          observerService.addObserver(this, "http-on-modify-request", false);
          observerService.addObserver(this, "http-on-examine-response", false);
        }
        return;
      } catch(e) {
        this.errlog("observe exception: "+e);
      }
  }

};

var NSGetFactory = XPCOMUtils.generateNSGetFactory([OpenIDNascarHttpListener]);
